package edu.yourl.persistence;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springmodules.db4o.support.Db4oDaoSupport;

import com.db4o.ObjectSet;
import com.db4o.ext.Db4oException;
import com.db4o.query.Predicate;
import com.db4o.query.Query;

import edu.yourl.domain.Storeable;

public abstract class AbstractRepository extends Db4oDaoSupport implements IAbstractRepository{

	/**
	 * se guarda un objeto en la db
	 */
	public void add(Storeable o) {
		//set está deprecado , pero el db4o template esta viejo y lo usa
		//igual, el set llama al nuevo "store"
		this.getDb4oTemplate().set(o);
		
	}
	
	/**
	 * se le pasa un predicado que tiene que overridear un "matches" y devuelve cierto tipo de objetos 
	 * que matchean con ese predicate
	 */
	protected <T extends Storeable> List<T> get(Predicate<T> p) {
		@SuppressWarnings("unchecked")
		ObjectSet<T> result = this.getDb4oTemplate().query(p);
		return new ArrayList<T>(result);
	}
	
	
	protected <T extends Storeable> T getObjectByPropertyValue(Class<T> clazz, final String propertyName, final Object propertyExpectedValue) {
		List<T> results = this.getByPropertyValue(clazz, propertyName, propertyExpectedValue);
		if (results.size() > 1) {
			throw new Db4oException();
		}
		return results.size() == 1 ? results.get(0) : null;
	}
	
	protected <T extends Storeable> List<T> getByPropertyValue(Class<T> clazz, final String propertyName, final Object propertyExpectedValue) {
		Predicate<T> predicate = new Predicate<T>(clazz) {

			private static final long serialVersionUID = 1L;

			public boolean match(T arg0) {
				BeanWrapper wrapper = new BeanWrapperImpl(arg0);
				Object objectValue = wrapper.getPropertyValue(propertyName);
				if (propertyExpectedValue == null) {
					return objectValue == null;
				}
				return propertyExpectedValue.equals(objectValue);
			};
		};
		return this.get(predicate);
	}
	
	protected <T extends Storeable> List<T> getByPropertyValue(Class<T> clazz, final List<QueryTandem> tandemList) {
		Predicate<T> predicate = new Predicate<T>(clazz) {

			private static final long serialVersionUID = 1L;

			public boolean match(T arg0) {
				BeanWrapper wrapper = new BeanWrapperImpl(arg0);
				for (QueryTandem tandem : tandemList) {
					Object objectValue = wrapper.getPropertyValue(tandem.getPropertyName());
					if (tandem.getExpectedValue() == null) {
						if (objectValue != null) {
							return false;
						}
					} else {
						if (!tandem.getExpectedValue().equals(objectValue)) {
							return false;
						}
					}
				}
				return true;
			};
		};
		return this.get(predicate);

	}
	
	/**
	 * Sirve para pedir un solo objeto, con determinado predicado.
	 * Si trae más de uno, se lanza una excepcion
	 */
	public <T extends Storeable> T getObject(Predicate<T> p) {
		@SuppressWarnings("unchecked")
		ObjectSet<T> objectSet = this.getDb4oTemplate().query(p);
		if (objectSet.size() > 1) {
			throw new Db4oException();
		}
		return objectSet.size() == 1 ? objectSet.get(0) : null;
	}
	
	@SuppressWarnings("unchecked")
	public <T extends Storeable> T getByDb4oId(long id){
		return (T)this.getDb4oTemplate().getByID(id);
	}
	
	
	/**
	 * Limpia la base entera. No está en la interfaz, no me parece correcto ponerla
	 */
	public void cleanDb() {
		Query query = this.getDb4oTemplate().query();
		query.constrain(Storeable.class);
		@SuppressWarnings("unchecked")
		ObjectSet<Storeable> result = query.execute();
		while (result.hasNext()) {
			this.getDb4oTemplate().delete(result.next());
		}
	}
	
	/**
	 * 
	 * Devuelve todos los objetos de una clase en particular que se le pasa como parametro
	 * 
	 * 
	 * @param <T> Clase de la cual se quieren todos los objetos.
	 * @param param
	 * @return lista de los objetos encontrados
	 */
	public <T extends Storeable> List<T> getAll(Class<T> param) {
		Query query = this.getDb4oTemplate().query();
		query.constrain(param);
		@SuppressWarnings("unchecked")
		ObjectSet<T> result = query.execute();
		List<T> list = new LinkedList<T>(result);
		return list;
	}
	
	
	
}
